using System;
using System.Collections.Generic;
using System.Text;
using SlimDX.XInput;
using SlimDX;

namespace Barrage.Global
{
    /// <summary>
    /// Defines a trigger of a gamepad
    /// </summary>
    public enum Trigger
    {
        /// <summary>
        /// Left gamepad trigger
        /// </summary>
        LeftTrigger,
        /// <summary>
        /// Right gamepad trigger
        /// </summary>
        RightTrigger
    }
    /// <summary>
    /// Defines a stick direction of a gamepad
    /// </summary>
    public enum StickDirection
    {
        /// <summary>
        /// Left direction
        /// </summary>
        Left,
        /// <summary>
        /// Right direction
        /// </summary>
        Right,
        /// <summary>
        /// Up direction
        /// </summary>
        Up,
        /// <summary>
        /// Down direction
        /// </summary>
        Down
    }
    /// <summary>
    /// Defines a stick from a gamepad
    /// </summary>
    public enum Stick
    {
        /// <summary>
        /// Left stick
        /// </summary>
        LeftStick,
        /// <summary>
        /// Right Stick
        /// </summary>
        RightStick
    }
    /// <summary>
    /// Defines a XInput compatible Gamepad. These can be assigned to a player index, ranging from 1 to 4.
    /// </summary>
    public class Gamepad:IDisposable
    {
        static Gamepad[] gamepads = new Gamepad[4];

        Controller controller;
        SlimDX.XInput.Gamepad previousState;
        SlimDX.XInput.Gamepad currentState;

        UserIndex index;
        /// <summary>
        /// Gets this gamepad instance's player index
        /// </summary>
        public UserIndex Index { get { return index; } }

        float deadZone = 0.15f;
        /// <summary>
        /// Gets / Sets the gamepad's dead zone for position calculations
        /// </summary>
        public float DeadZone { get { return deadZone; } set { deadZone = value; } }

        /// <summary>
        /// Tells if the gamepad controller is connected or not
        /// </summary>
        public bool Connected { get { return controller.IsConnected; } }


        Gamepad(UserIndex index)
        {
            this.index = index;
            controller = new Controller(index);
            if (Connected)
            {
                currentState = controller.GetState().Gamepad;
                previousState = currentState;
            }
            Engine.RegisterDisposeHandler(Dispose);
        }

        /// <summary>
        /// Gets the gamepad instance for the specified player index
        /// </summary>
        /// <param name="index">Player index</param>
        /// <returns>The gamepad instance for the specified player index</returns>
        public static Gamepad Instance(UserIndex index)
        {
            if (index == UserIndex.Any) return null;
            if (gamepads[(int)index] == null)
                gamepads[(int)index] = new Gamepad(index);
            return gamepads[(int)index];
        }

        /// <summary>
        /// Updates every gamepad status
        /// </summary>
        public static void UpdateStatus()
        {
            foreach (Gamepad g in gamepads)
            {
                if (g != null)
                    g.UpdateControllerStatus();
            }
        }

        void UpdateControllerStatus()
        {
            previousState = currentState;
            if (Connected)
            {
                currentState = controller.GetState().Gamepad;

            }
            else
            {
                currentState = new SlimDX.XInput.Gamepad();
            }
        }

        /// <summary>
        /// Gets the current gamepad state
        /// </summary>
        public SlimDX.XInput.Gamepad CurrentState { get { return currentState; } }
        /// <summary>
        /// Gets the previous gamepad state
        /// </summary>
        public SlimDX.XInput.Gamepad PreviousState { get { return previousState; } }

        /// <summary>
        /// Gets the previous status of the specified button / buttons
        /// </summary>
        /// <param name="flags">Buttons to check</param>
        /// <returns>True if all the buttons specified were pressed, false otherwise</returns>
        public bool PreviousButtonStatus(GamepadButtonFlags flags)
        {
            return (int)(previousState.Buttons & flags) != 0;
        }

        /// <summary>
        /// Gets the current status of the specified button / buttons
        /// </summary>
        /// <param name="flags">Buttons to check</param>
        /// <returns>True if all the buttons specified are pressed, false otherwise</returns>
        public bool ButtonStatus(GamepadButtonFlags flags)
        {
            return (int)(currentState.Buttons & flags) != 0;
        }

        /// <summary>
        /// Gets the previous status of the specified button / buttons on any of the gamepads
        /// </summary>
        /// <param name="flags">Buttons to check</param>
        /// <returns>True if all the buttons specified were pressed on at least one gamepad, false otherwise</returns>
        public static bool AnyButtonStatus(GamepadButtonFlags flags)
        {
            return Gamepad.Instance(UserIndex.One).ButtonStatus(flags)
                || Gamepad.Instance(UserIndex.Two).ButtonStatus(flags)
                || Gamepad.Instance(UserIndex.Three).ButtonStatus(flags)
                || Gamepad.Instance(UserIndex.Four).ButtonStatus(flags);
        }

        /// <summary>
        /// Returns true if the button's state changed from released to pressed 
        /// </summary>
        /// <param name="flags">Button to check</param>
        /// <returns>True if the state changed to Pressed, false otherwise</returns>
        public bool ButtonPressed(GamepadButtonFlags flags)
        {
            return ButtonStatus(flags) && !PreviousButtonStatus(flags);
        }

        /// <summary>
        /// Returns true if the button's state changed from released to pressed on any of the gamepads
        /// </summary>
        /// <param name="flags">Button to check</param>
        /// <returns>True if the state changed to Pressed, false otherwise</returns>
        public static bool AnyButtonPressed(GamepadButtonFlags flags)
        {
            return Gamepad.Instance(UserIndex.One).ButtonPressed(flags)
                || Gamepad.Instance(UserIndex.Two).ButtonPressed(flags)
                || Gamepad.Instance(UserIndex.Three).ButtonPressed(flags)
                || Gamepad.Instance(UserIndex.Four).ButtonPressed(flags);
        }

        /// <summary>
        /// Returns true if the button's state changed from pressed to released 
        /// </summary>
        /// <param name="flags">Button to check</param>
        /// <returns>True if the state changed to Released, false otherwise</returns>
        public bool ButtonReleased(GamepadButtonFlags flags)
        {
            return !ButtonStatus(flags) && PreviousButtonStatus(flags);
        }

        /// <summary>
        /// Returns true if the button's state changed from pressed to released on any of the gamepads
        /// </summary>
        /// <param name="flags">Button to check</param>
        /// <returns>True if the state changed to Released, false otherwise</returns>
        public static bool AnyButtonReleased(GamepadButtonFlags flags)
        {
            return Gamepad.Instance(UserIndex.One).ButtonReleased(flags)
                || Gamepad.Instance(UserIndex.Two).ButtonReleased(flags)
                || Gamepad.Instance(UserIndex.Three).ButtonReleased(flags)
                || Gamepad.Instance(UserIndex.Four).ButtonReleased(flags);
        }
        
        /// <summary>
        /// Checks if the previous status of the specified stick was facing in the specified direction
        /// To considere a stick to be facing on any direction, the coordinate must be at least 50% faced to it (taking deadzone into consideration)
        /// </summary>
        /// <param name="axis">Axis to check</param>
        /// <param name="direction">Direction to check</param>
        /// <returns>True if the axis was facing in the specified direction</returns>
        public bool PreviousStickStatus(Stick axis, StickDirection direction)
        {
            Vector2 pos = PreviousStickPosition(axis);
            switch (direction)
            {
                case StickDirection.Up:
                    return pos.Y >= 0.5f;
                case StickDirection.Down:
                    return pos.Y <= -0.5f;
                case StickDirection.Left:
                    return pos.X <= -0.5f;
                case StickDirection.Right:
                    return pos.X >= 0.5f;
                default:
                    return false;
            }
        }


        /// <summary>
        /// Checks if the current status of the specified stick is facing in the specified direction.
        /// To considere a stick to be facing on any direction, the coordinate must be at least 50% faced to it (taking deadzone into consideration)
        /// </summary>
        /// <param name="axis">Axis to check</param>
        /// <param name="direction">Direction to check</param>
        /// <returns>True if the axis is facing in the specified direction</returns>
        public bool StickStatus(Stick axis, StickDirection direction)
        {
            Vector2 pos = StickPosition(axis);
            switch (direction)
            {
                case StickDirection.Up:
                    return pos.Y >= 0.5f;
                case StickDirection.Down:
                    return pos.Y <= -0.5f;
                case StickDirection.Left:
                    return pos.X <= -0.5f;
                case StickDirection.Right:
                    return pos.X >= 0.5f;
                default:
                    return false;
            }
        }

        /// <summary>
        /// Checks if the current status of any stick is facing in the specified direction.
        /// To considere a stick to be facing on any direction, the coordinate must be at least 50% faced to it (taking deadzone into consideration)
        /// </summary>
        /// <param name="axis">Axis to check</param>
        /// <param name="direction">Direction to check</param>
        /// <returns>True if the axis is facing in the specified direction on any of the gamepads</returns>
        public static bool AnyStickStatus(Stick axis, StickDirection direction)
        {
            return Gamepad.Instance(UserIndex.One).StickStatus(axis, direction)
                || Gamepad.Instance(UserIndex.Two).StickStatus(axis, direction)
                || Gamepad.Instance(UserIndex.Three).StickStatus(axis, direction)
                || Gamepad.Instance(UserIndex.Four).StickStatus(axis, direction);
        }

        /// <summary>
        /// Checks if the stick's state changed and now is facing the specified direction
        /// </summary>
        /// <param name="axis">Axis to check</param>
        /// <param name="direction">Direction to check</param>
        /// <returns>True if the axis turned to that direction</returns>
        public bool StickPressed(Stick axis, StickDirection direction)
        {
            return StickStatus(axis, direction) && !PreviousStickStatus(axis, direction);
        }

        /// <summary>
        /// Checks if the stick's state changed and now is facing the specified direction on any gamepad
        /// </summary>
        /// <param name="axis">Axis to check</param>
        /// <param name="direction">Direction to check</param>
        /// <returns>True if the axis turned to that direction</returns>
        public static bool AnyStickPressed(Stick axis, StickDirection direction)
        {
            return Gamepad.Instance(UserIndex.One).StickPressed(axis,direction)
                || Gamepad.Instance(UserIndex.Two).StickPressed(axis, direction)
                || Gamepad.Instance(UserIndex.Three).StickPressed(axis, direction)
                || Gamepad.Instance(UserIndex.Four).StickPressed(axis, direction);
        }

        /// <summary>
        /// Checks if the stick's state changed and now is not facing the specified direction
        /// </summary>
        /// <param name="axis">Axis to check</param>
        /// <param name="direction">Direction to check</param>
        /// <returns>True if the axis left to be turned to that direction</returns>
        public bool StickReleased(Stick axis, StickDirection direction)
        {
            return !StickStatus(axis, direction) && PreviousStickStatus(axis, direction);
        }

        /// <summary>
        /// Checks if the stick's state changed and now is not facing the specified direction on any gamepad
        /// </summary>
        /// <param name="axis">Axis to check</param>
        /// <param name="direction">Direction to check</param>
        /// <returns>True if the axis left to be turned to that direction</returns>
        public static bool AnyStickReleased(Stick axis, StickDirection direction)
        {
            return Gamepad.Instance(UserIndex.One).StickReleased(axis, direction)
                || Gamepad.Instance(UserIndex.Two).StickReleased(axis, direction)
                || Gamepad.Instance(UserIndex.Three).StickReleased(axis, direction)
                || Gamepad.Instance(UserIndex.Four).StickReleased(axis, direction);
        }
        
        /// <summary>
        /// Gets the previous stick position, with dead zone applied
        /// </summary>
        /// <param name="axis">Axis to check</param>
        /// <returns>Vector2 with coordinate values</returns>
        public Vector2 PreviousStickPosition(Stick axis)
        {
            Vector2 value;
            switch (axis)
            {
                default:
                case Stick.LeftStick:
                    value = new Vector2((float)previousState.LeftThumbX / 32767, (float)previousState.LeftThumbY / 32767);
                    break;
                case Stick.RightStick:
                    value = new Vector2((float)previousState.RightThumbX / 32767, (float)previousState.RightThumbY / 32767);
                    break;
            }
            int s1 = Math.Sign(value.X);
            int s2 = Math.Sign(value.Y);

            value = GameMath.VectorMin(new Vector2(1, 1), new Vector2((Math.Max(0, Math.Abs(value.X) - deadZone)) / (1 - deadZone), (Math.Max(0, Math.Abs(value.Y) - deadZone)) / (1 - deadZone)));
            value = new Vector2(value.X * s1, value.Y * s2);
            
            return value;
        }

        /// <summary>
        /// Gets the current stick position, with dead zone applied
        /// </summary>
        /// <param name="axis">Axis to check</param>
        /// <returns>Vector2 with coordinate values</returns>
        public Vector2 StickPosition(Stick axis)
        {
            Vector2 value;
            switch (axis)
            {
                default:
                case Stick.LeftStick:
                    value = new Vector2((float)currentState.LeftThumbX / 32767, (float)currentState.LeftThumbY / 32767);
                    break;
                case Stick.RightStick:
                    value = new Vector2((float)currentState.RightThumbX / 32767, (float)currentState.RightThumbY / 32767);
                    break;
            }
            int s1 = Math.Sign(value.X);
            int s2 = Math.Sign(value.Y);

            value = GameMath.VectorMin(new Vector2(1, 1),new Vector2((Math.Max(0, Math.Abs(value.X) - deadZone)) / (1 - deadZone), (Math.Max(0, Math.Abs(value.Y) - deadZone)) / (1 - deadZone)));
            value = new Vector2(value.X*s1,value.Y*s2);
           
            return value;
        }

        /// <summary>
        /// Sums the stick positions of all gamepads
        /// </summary>
        /// <param name="axis">Axis to check</param>
        /// <returns>Vector2 with coordinate values</returns>
        public static Vector2 SumStickPositions(Stick axis)
        {
            return Gamepad.Instance(UserIndex.One).StickPosition(axis) +
                Gamepad.Instance(UserIndex.Two).StickPosition(axis) +
                Gamepad.Instance(UserIndex.Three).StickPosition(axis) +
                Gamepad.Instance(UserIndex.Four).StickPosition(axis);
        }
        
        /// <summary>
        /// Gets the current trigger position
        /// </summary>
        /// <param name="trigger">Trigger to check</param>
        /// <returns>Trigger state, ranging from 0 to 1</returns>
        public float TriggerPosition(Trigger trigger)
        {
            switch (trigger)
            {
                default:
                case Trigger.LeftTrigger:
                    return Math.Max(0, (currentState.LeftTrigger / 255.0f - deadZone) * (1 - deadZone));
                case Trigger.RightTrigger:
                    return Math.Max(0, (currentState.RightTrigger / 255.0f - deadZone)) * (1 - deadZone);
            }
        }

        /// <summary>
        /// Sets the gamepad's vibration value (sets the same value for both engines)
        /// </summary>
        /// <param name="vibration">Vibration value, from 0 to 1</param>
        public void setVibration(float vibration)
        {
            this.setVibration(vibration, vibration);
        }


        /// <summary>
        /// Sets the gamepad's vibration value for every engine
        /// </summary>
        /// <param name="leftVibration">Vibration value, from 0 to 1</param>
        /// <param name="rightVibration">Vibration value, from 0 to 1</param>
        public void setVibration(float leftVibration, float rightVibration)
        {
            if (!Connected) return;
            Vibration v = new Vibration();
            v.LeftMotorSpeed = (ushort)(leftVibration * 65535);
            v.RightMotorSpeed = (ushort)(rightVibration * 65535);
            controller.SetVibration(v);
        }

        /// <summary>
        /// Clear resources
        /// </summary>
        public void Dispose()
        {
            if (gamepads[(int)index] ==this)
                gamepads[(int)index] = null;
            controller = null;
            NLog.LogManager.GetLogger(ToString()).Debug("Disposing");
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Destructor. Clear resources
        /// </summary>
        ~Gamepad()
        {
            Dispose();
        }
    }
}
